﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using ShevaEngine.Core.Interfaces;
using ShevaEngine.Core.Modules.Graphics;
using ShevaEngine.Core.Modules.Graphics.Lights;
using ShevaEngine.Core.Modules.Materials;
using ShevaEngine.Core.Modules.Scenes;
using ShevaEngine.Core.Modules.Graphics.Cameras;
using System.IO;

namespace ShevaDeferredRenderer.Shadows
{
    /// <summary>
    /// Dual paraboloid SM.
    /// </summary>
    public class DualParaboloidSM : ShadowMap
    {
         /// <summary>Shadow map.</summary>
        public RenderTarget2D ShadowMapTop;
        /// <summary>Shadow map.</summary>
        public RenderTarget2D ShadowMapBottom;
        /// <summary>Shadow map size.</summary>
        public Int32 ShadowMapSize;
        /// <summary>Light camera.</summary>
        private Matrix LightViewCamera;
        /// <summary>Shadow map effect.</summary>
        private Effect ShadowMapEffect;
        /// <summary>Last draw call.</summary>
        private RenderingPackage LastRenderingPair;
        /// <summary>Initial draw call.</summary>
        private RenderingPackage InitialRenderingPair;
        /// <summary>Last material.</summary>
        private GraphicsMaterial LastMaterial;
        /// <summary>InstancesVBO.</summary>
        private DynamicVertexBuffer InstancesVertexBuffer;
        /// <summary>Matrices.</summary>
        private List<Matrix> Matrices;        

        /// <summary>
        /// Constructor.
        /// </summary>
        public DualParaboloidSM()
        {
            this.ShadowMapSize = 1024;

            this.ShadowMapTop = new RenderTarget2D(ShevaEngine.Core.ShevaEngine.Instance.GraphicsDevice,
                ShadowMapSize, ShadowMapSize, false, SurfaceFormat.Color, DepthFormat.Depth24Stencil8);
            this.ShadowMapBottom = new RenderTarget2D(ShevaEngine.Core.ShevaEngine.Instance.GraphicsDevice,
                ShadowMapSize, ShadowMapSize, false, SurfaceFormat.Color, DepthFormat.Depth24Stencil8);

            this.ShadowMapEffect = MaterialManager.Instance.GetEffect(@"Shadows\DualParaboloidSM");

            this.InitialRenderingPair = new RenderingPackage();
            this.InitialRenderingPair.DrawCall = DrawCallManager.Instance.CreateIndexedPrimitivesDrawCall(PrimitiveType.LineList, 0, 0, 0, 0, 0);
            this.InitialRenderingPair.DrawCallKey = new DrawCallKey(UInt16.MaxValue, UInt16.MaxValue, UInt16.MaxValue, UInt16.MaxValue);

            this.Matrices = new List<Matrix>();

            this.InstancesVertexBuffer = new DynamicVertexBuffer(ShevaEngine.Core.ShevaEngine.Instance.GraphicsDevice,
                        InstanceVertexDeclaration.BlendWeight, 128, BufferUsage.WriteOnly);
        }


        /// <summary>
        /// Method creates shadow map.
        /// </summary>
        public override void CreateShadowMap(Light light, Scene scene)
        {
            ShevaEngine.Core.ShevaEngine.Instance.GraphicsDevice.SetRenderTarget(this.ShadowMapTop);

            ShevaEngine.Core.ShevaEngine.Instance.GraphicsDevice.Clear(ClearOptions.DepthBuffer | ClearOptions.Target, Color.White, 1.0f, 1);

            ShevaEngine.Core.ShevaEngine.Instance.GraphicsDevice.BlendState = BlendState.Opaque;
            //GraphicsEngine.Instance.GraphicsDevice.RenderState.AlphaBlendEnable = false;            

            DepthStencilState depthStencilState = new DepthStencilState();
            depthStencilState.DepthBufferEnable = true;
            depthStencilState.DepthBufferWriteEnable = true;
            depthStencilState.DepthBufferFunction = CompareFunction.Less;
            ShevaEngine.Core.ShevaEngine.Instance.GraphicsDevice.DepthStencilState = depthStencilState;

            {
                RasterizerState rasterizerState = new RasterizerState();
                rasterizerState.CullMode = CullMode.CullCounterClockwiseFace;                
                ShevaEngine.Core.ShevaEngine.Instance.GraphicsDevice.RasterizerState = rasterizerState;
            }

            this.LightViewCamera = Matrix.CreateLookAt(
                light.World.Translation, light.World.Translation - new Vector3(0, 1, 0), Vector3.UnitZ);

            RenderingPipeline pipeline = scene.GetRenderingPipeline(light.World.Translation, light.Radius * light.Radius);            

            this.ShadowMapEffect.Parameters["ShadowMapFarPlane"].SetValue(light.Radius);
            this.ShadowMapEffect.Parameters["View"].SetValue(this.LightViewCamera);

            this.ShadowMapEffect.Parameters["ShadowMapDirection"].SetValue(1.0f);

            this.ShadowMapEffect.CurrentTechnique.Passes[0].Apply();

            this.RenderPipeline(pipeline);

            ShevaEngine.Core.ShevaEngine.Instance.GraphicsDevice.SetRenderTarget(this.ShadowMapBottom);

            ShevaEngine.Core.ShevaEngine.Instance.GraphicsDevice.Clear(ClearOptions.DepthBuffer | ClearOptions.Target, Color.White, 1.0f, 1);

            {
                RasterizerState rasterizerState = new RasterizerState();
                rasterizerState.CullMode = CullMode.CullClockwiseFace;                
                ShevaEngine.Core.ShevaEngine.Instance.GraphicsDevice.RasterizerState = rasterizerState;
            }

            this.ShadowMapEffect.Parameters["ShadowMapDirection"].SetValue(-1.0f);

            this.RenderPipeline(pipeline);

            ShevaEngine.Core.ShevaEngine.Instance.GraphicsDevice.SetRenderTarget(null);

            //using (StreamWriter sw = new StreamWriter(@"C:\Marek\SMTop.jpg"))
            //{
            //    this.ShadowMapTop.SaveAsJpeg(sw.BaseStream, this.ShadowMapSize, this.ShadowMapSize);
            //}

            //using (StreamWriter sw = new StreamWriter(@"C:\Marek\SMBottom.jpg"))
            //{
            //    this.ShadowMapBottom.SaveAsJpeg(sw.BaseStream, this.ShadowMapSize, this.ShadowMapSize);
            //}
        }        
        
        /// <summary>
        /// Render pipeline method.
        /// </summary>
        private void RenderPipeline(RenderingPipeline pipeline)
        {
            this.LastRenderingPair = this.InitialRenderingPair;
            this.Matrices.Clear();                       

            for (int i = 0; i < pipeline.ActualCount; i++)
            {
                if (pipeline.DrawCalls[i].DrawCall != this.LastRenderingPair.DrawCall)
                {
                    if (this.Matrices.Count > 0)
                        DrawCall.RenderDrawCalls(this.ShadowMapEffect, this.LastRenderingPair.DrawCall, this.Matrices.ToArray());

                    this.Matrices.Clear();

                    if (pipeline.DrawCalls[i].DrawCallKey.MaterialID != this.LastRenderingPair.DrawCallKey.MaterialID)
                    {
                        this.LastMaterial = MaterialManager.Instance[pipeline.DrawCalls[i].DrawCallKey.MaterialID].GraphicsProfile;

                        if (!this.LastMaterial.CastShadows)
                        {
                            this.LastRenderingPair = pipeline.DrawCalls[i];

                            continue;
                        }
                    }

                    if (pipeline.DrawCalls[i].DrawCallKey.TextureState != this.LastRenderingPair.DrawCallKey.TextureState)
                    {
                        TextureState textureState = TextureManager.Instance[pipeline.DrawCalls[i].DrawCallKey.TextureState];

                        //if (textureState.ContainsKey("Diffuse"))
                            //this.ShadowMapEffect.Parameters["Diffuse"].SetValue(textureState["Diffuse"]);
                    }

                    if (pipeline.DrawCalls[i].DrawCallKey.MaterialParamsID == DrawCallKey.None)
                        this.ShadowMapEffect.CurrentTechnique = this.ShadowMapEffect.Techniques["NoAnimations"];
                    else if (pipeline.DrawCalls[i].DrawCallKey.MaterialParamsID != this.LastRenderingPair.DrawCallKey.MaterialParamsID)                        
                    {
                        if (this.LastMaterial.ParametersManager[pipeline.DrawCalls[i].DrawCallKey.MaterialParamsID].IsAnimated)
                        {
                            this.LastMaterial.ParametersManager[pipeline.DrawCalls[i].DrawCallKey.MaterialParamsID].AnimatedParameters.Set(
                                this.ShadowMapEffect);

                            this.ShadowMapEffect.CurrentTechnique = this.ShadowMapEffect.Techniques["WithAnimations"];
                        }
                        else
                            this.ShadowMapEffect.CurrentTechnique = this.ShadowMapEffect.Techniques["NoAnimations"];
                    }

                    if (pipeline.DrawCalls[i].DrawCallKey.BuffersID != DrawCallKey.None)
                        GraphicsBuffersManager.Instance.SetBuffers(pipeline.DrawCalls[i].DrawCallKey.BuffersID);

                    this.Matrices.Add(pipeline.DrawCalls[i].Matrix);            

                    this.LastRenderingPair = pipeline.DrawCalls[i];
                }
                else if (this.LastMaterial.CastShadows)
                    this.Matrices.Add(pipeline.DrawCalls[i].Matrix);            
            }

            if (this.Matrices.Count > 0)
                DrawCall.RenderDrawCalls(this.ShadowMapEffect, this.LastRenderingPair.DrawCall, this.Matrices.ToArray());            
        }        
    }
}
